home *** CD-ROM | disk | FTP | other *** search
/ Aminet 40 / Aminet 40 (2000)(Schatztruhe)[!][Dec 2000].iso / Aminet / dev / lang / Python16_Src.lha / Python16_Source / Tools / audiopy / audiopy next >
Encoding:
Text File  |  2000-03-30  |  16.3 KB  |  509 lines

  1. #! /usr/bin/env python
  2.  
  3. """audiopy -- a program to control the Solaris audio device.
  4.  
  5. Contact: Barry Warsaw
  6. Email:   bwarsaw@python.org
  7. Version: %(__version__)s
  8.  
  9. When no arguments are given, this pops up a graphical window which lets you
  10. choose the audio input and output devices, and set the output volume.
  11.  
  12. This program can be driven via the command line, and when done so, no window
  13. pops up.  Most options have the general form:
  14.  
  15.     --device[={0,1}]
  16.     -d[={0,1}]
  17.         Set the I/O device.  With no value, it toggles the specified device.
  18.         With a value, 0 turns the device off and 1 turns the device on.
  19.  
  20. The list of devices and their short options are:
  21.  
  22.  (input)
  23.     microphone  -- m
  24.     linein      -- i
  25.     cd          -- c
  26.  
  27.  (output)
  28.     headphones  -- p
  29.     speaker     -- s
  30.     lineout     -- o
  31.  
  32. Other options are:
  33.  
  34.     --gain volume
  35.     -g volume
  36.         Sets the output gain to the specified volume, which must be an integer
  37.         in the range [%(MIN_GAIN)s..%(MAX_GAIN)s]
  38.  
  39.     --version
  40.     -v
  41.         Print the version number and exit.
  42.  
  43.     --help
  44.     -h
  45.         Print this message and exit.
  46. """
  47.  
  48. import sys
  49. import os
  50. import string
  51. import errno
  52. import sunaudiodev
  53. from SUNAUDIODEV import *
  54.  
  55. # Milliseconds between interrupt checks
  56. KEEPALIVE_TIMER = 500
  57.  
  58. __version__ = '1.1'
  59.  
  60.  
  61.  
  62. class MainWindow:
  63.     def __init__(self, device):
  64.         from Tkinter import *
  65.         self.__helpwin = None
  66.         self.__devctl = device
  67.         info = device.getinfo()
  68.         #
  69.         self.__tkroot = tkroot = Tk(className='Audiopy')
  70.         tkroot.withdraw()
  71.         # create the menubar
  72.         menubar = Menu(tkroot)
  73.         filemenu = Menu(menubar, tearoff=0)
  74.         filemenu.add_command(label='Quit',
  75.                              command=self.__quit,
  76.                              accelerator='Alt-Q',
  77.                              underline=0)
  78.         helpmenu = Menu(menubar, name='help', tearoff=0)
  79.         helpmenu.add_command(label='About Audiopy...',
  80.                              command=self.__popup_about,
  81.                              underline=0)
  82.         helpmenu.add_command(label='Help...',
  83.                              command=self.__popup_using,
  84.                              underline=0)
  85.         menubar.add_cascade(label='File',
  86.                             menu=filemenu,
  87.                             underline=0)
  88.         menubar.add_cascade(label='Help',
  89.                             menu=helpmenu,
  90.                             underline=0)
  91.         # now create the top level window
  92.         root = self.__root = Toplevel(tkroot, class_='Audiopy', menu=menubar)
  93.         root.protocol('WM_DELETE_WINDOW', self.__quit)
  94.         root.title('audiopy ' + __version__)
  95.         root.iconname('audiopy ' + __version__)
  96.         root.tk.createtimerhandler(KEEPALIVE_TIMER, self.__keepalive)
  97.         #
  98.         buttons = []
  99.         #
  100.         # where does input come from?
  101.         frame = Frame(root, bd=1, relief=RAISED)
  102.         frame.grid(row=1, column=0, sticky='NSEW')
  103.         label = Label(frame, text='Input From:')
  104.         label.grid(row=0, column=0, sticky=E)
  105.         self.__inputvar = IntVar()
  106.         ##
  107.         btn = Radiobutton(frame,
  108.                           text='None',
  109.                           variable=self.__inputvar,
  110.                           value=0,
  111.                           command=self.__pushtodev,
  112.                           underline=0)
  113.         btn.grid(row=0, column=1, sticky=W)
  114.         root.bind('<Alt-n>', self.__none)
  115.         root.bind('<Alt-N>', self.__none)
  116.         if not info.i_avail_ports & MICROPHONE:
  117.             btn.configure(state=DISABLED)
  118.         buttons.append(btn)
  119.         ##
  120.         btn = Radiobutton(frame,
  121.                           text='Microphone',
  122.                           variable=self.__inputvar,
  123.                           value=MICROPHONE,
  124.                           command=self.__pushtodev,
  125.                           underline=0)
  126.         btn.grid(row=1, column=1, sticky=W)
  127.         root.bind('<Alt-m>', self.__mic)
  128.         root.bind('<Alt-M>', self.__mic)
  129.         if not info.i_avail_ports & MICROPHONE:
  130.             btn.configure(state=DISABLED)
  131.         buttons.append(btn)
  132.         ##
  133.         btn = Radiobutton(frame,
  134.                           text='Line In',
  135.                           variable=self.__inputvar,
  136.                           value=LINE_IN,
  137.                           command=self.__pushtodev,
  138.                           underline=5)
  139.         btn.grid(row=2, column=1, sticky=W)
  140.         root.bind('<Alt-i>', self.__linein)
  141.         root.bind('<Alt-I>', self.__linein)
  142.         if not info.i_avail_ports & LINE_IN:
  143.             btn.configure(state=DISABLED)
  144.         buttons.append(btn)
  145.         ## if SUNAUDIODEV was built on an older version of Solaris, the CD
  146.         ## input device won't exist
  147.         try:
  148.             btn = Radiobutton(frame,
  149.                               text='CD',
  150.                               variable=self.__inputvar,
  151.                               value=CD,
  152.                               command=self.__pushtodev,
  153.                               underline=0)
  154.             btn.grid(row=3, column=1, sticky=W)
  155.             root.bind('<Alt-c>', self.__cd)
  156.             root.bind('<Alt-C>', self.__cd)
  157.             if not info.i_avail_ports & CD:
  158.                 btn.configure(state=DISABLED)
  159.             buttons.append(btn)
  160.         except NameError:
  161.             pass
  162.         #
  163.         # where does output go to?
  164.         frame = Frame(root, bd=1, relief=RAISED)
  165.         frame.grid(row=2, column=0, sticky='NSEW')
  166.         label = Label(frame, text='Output To:')
  167.         label.grid(row=0, column=0, sticky=E)
  168.         self.__spkvar = IntVar()
  169.         btn = Checkbutton(frame,
  170.                           text='Speaker',
  171.                           variable=self.__spkvar,
  172.                           onvalue=SPEAKER,
  173.                           command=self.__pushtodev,
  174.                           underline=0)
  175.         btn.grid(row=0, column=1, sticky=W)
  176.         root.bind('<Alt-s>', self.__speaker)
  177.         root.bind('<Alt-S>', self.__speaker)
  178.         if not info.o_avail_ports & SPEAKER:
  179.             btn.configure(state=DISABLED)
  180.         buttons.append(btn)
  181.         ##
  182.         self.__headvar = IntVar()
  183.         btn = Checkbutton(frame,
  184.                           text='Headphones',
  185.                           variable=self.__headvar,
  186.                           onvalue=HEADPHONE,
  187.                           command=self.__pushtodev,
  188.                           underline=4)
  189.         btn.grid(row=1, column=1, sticky=W)
  190.         root.bind('<Alt-p>', self.__headphones)
  191.         root.bind('<Alt-P>', self.__headphones)
  192.         if not info.o_avail_ports & HEADPHONE:
  193.             btn.configure(state=DISABLED)
  194.         buttons.append(btn)
  195.         ##
  196.         self.__linevar = IntVar()
  197.         btn = Checkbutton(frame,
  198.                           variable=self.__linevar,
  199.                           onvalue=LINE_OUT,
  200.                           text='Line Out',
  201.                           command=self.__pushtodev,
  202.                           underline=0)
  203.         btn.grid(row=2, column=1, sticky=W)
  204.         root.bind('<Alt-l>', self.__lineout)
  205.         root.bind('<Alt-L>', self.__lineout)
  206.         if not info.o_avail_ports & LINE_OUT:
  207.             btn.configure(state=DISABLED)
  208.         buttons.append(btn)
  209.         #
  210.         # Fix up widths
  211.         widest = 0
  212.         for b in buttons:
  213.             width = b['width']
  214.             if width > widest:
  215.                 widest = width
  216.         for b in buttons:
  217.             b.configure(width=widest)
  218.         # root bindings
  219.         root.bind('<Alt-q>', self.__quit)
  220.         root.bind('<Alt-Q>', self.__quit)
  221.         #
  222.         # Volume
  223.         frame = Frame(root, bd=1, relief=RAISED)
  224.         frame.grid(row=3, column=0, sticky='NSEW')
  225.         label = Label(frame, text='Output Volume:')
  226.         label.grid(row=0, column=0, sticky=W)
  227.         self.__scalevar = IntVar()
  228.         self.__scale = Scale(frame,
  229.                              orient=HORIZONTAL,
  230.                              from_=MIN_GAIN,
  231.                              to=MAX_GAIN,
  232.                              length=200,
  233.                              variable=self.__scalevar,
  234.                              command=self.__volume)
  235.         self.__scale.grid(row=1, column=0, sticky=EW)
  236.         #
  237.         # do we need to poll for changes?
  238.         self.__needtopoll = 1
  239.         try:
  240.             fd = self.__devctl.fileno()
  241.             self.__needtopoll = 0
  242.         except AttributeError:
  243.             pass
  244.         else:
  245.             import fcntl
  246.             import signal
  247.             import STROPTS
  248.             # set up the signal handler
  249.             signal.signal(signal.SIGPOLL, self.__update)
  250.             fcntl.ioctl(fd, STROPTS.I_SETSIG, STROPTS.S_MSG)
  251.             self.__update()
  252.         
  253.     def __quit(self, event=None):
  254.         self.__devctl.close()
  255.         self.__root.quit()
  256.  
  257.     def __popup_about(self, event=None):
  258.         import tkMessageBox
  259.         tkMessageBox.showinfo('About Audiopy ' + __version__,
  260.                               '''\
  261. Audiopy %s
  262. Control the Solaris audio device
  263.  
  264. For information
  265. Contact: Barry A. Warsaw
  266. Email:   bwarsaw@python.org''' % __version__)
  267.  
  268.     def __popup_using(self, event=None):
  269.         if not self.__helpwin:
  270.             self.__helpwin = Helpwin(self.__tkroot, self.__quit)
  271.         self.__helpwin.deiconify()
  272.             
  273.  
  274.     def __keepalive(self):
  275.         # Exercise the Python interpreter regularly so keyboard interrupts get
  276.         # through.
  277.         self.__tkroot.tk.createtimerhandler(KEEPALIVE_TIMER, self.__keepalive)
  278.         if self.__needtopoll:
  279.             self.__update()
  280.  
  281.     def __update(self, num=None, frame=None):
  282.         # It's possible (although I have never seen it) to get an interrupted
  283.         # system call during the getinfo() call.  If so, and we're polling,
  284.         # don't sweat it because we'll come around again later.  Otherwise,
  285.         # we'll give it a couple of tries and then give up until next time.
  286.         tries = 0
  287.         while 1:
  288.             try:
  289.                 info = self.__devctl.getinfo()
  290.                 break
  291.             except sunaudiodev.error:
  292.                 if self.__needtopoll or tries > 3:
  293.                     return
  294.                 tries = tries + 1
  295.         # input
  296.         self.__inputvar.set(info.i_port)
  297.         # output
  298.         self.__spkvar.set(info.o_port & SPEAKER)
  299.         self.__headvar.set(info.o_port & HEADPHONE)
  300.         self.__linevar.set(info.o_port & LINE_OUT)
  301.         # volume
  302.         self.__scalevar.set(info.o_gain)
  303.  
  304.     def __pushtodev(self, event=None):
  305.         info = self.__devctl.getinfo()
  306.         info.o_port = self.__spkvar.get() + \
  307.                       self.__headvar.get() + \
  308.                       self.__linevar.get()
  309.         info.i_port = self.__inputvar.get()
  310.         info.o_gain = self.__scalevar.get()
  311.         try:
  312.             self.__devctl.setinfo(info)
  313.         except sunaudiodev.error, msg:
  314.             # TBD: what to do?  it's probably temporary.
  315.             pass
  316.  
  317.     def __getset(self, var, onvalue):
  318.         if var.get() == onvalue:
  319.             var.set(0)
  320.         else:
  321.             var.set(onvalue)
  322.         self.__pushtodev()
  323.  
  324.     def __none(self, event=None):
  325.         self.__inputvar.set(0)
  326.         self.__pushtodev()
  327.  
  328.     def __mic(self, event=None):
  329.         self.__getset(self.__inputvar, MICROPHONE)
  330.  
  331.     def __linein(self, event=None):
  332.         self.__getset(self.__inputvar, LINE_IN)
  333.  
  334.     def __cd(self, event=None):
  335.         self.__getset(self.__inputvar, CD)
  336.  
  337.     def __speaker(self, event=None):
  338.         self.__getset(self.__spkvar, SPEAKER)
  339.  
  340.     def __headphones(self, event=None):
  341.         self.__getset(self.__headvar, HEADPHONE)
  342.  
  343.     def __lineout(self, event=None):
  344.         self.__getset(self.__linevar, LINE_OUT)
  345.  
  346.     def __volume(self, event=None):
  347.         self.__pushtodev()
  348.  
  349.     def start(self):
  350.         self.__keepalive()
  351.         self.__tkroot.mainloop()
  352.  
  353.  
  354.  
  355. class Helpwin:
  356.     def __init__(self, master, quitfunc):
  357.         from Tkinter import *
  358.         self.__root = root = Toplevel(master, class_='Audiopy')
  359.         root.protocol('WM_DELETE_WINDOW', self.__withdraw)
  360.         root.title('Audiopy Help Window')
  361.         root.iconname('Audiopy Help Window')
  362.         root.bind('<Alt-q>', quitfunc)
  363.         root.bind('<Alt-Q>', quitfunc)
  364.         root.bind('<Alt-w>', self.__withdraw)
  365.         root.bind('<Alt-W>', self.__withdraw)
  366.  
  367.         # more elaborate help is available in the README file
  368.         readmefile = os.path.join(sys.path[0], 'README')
  369.         try:
  370.             fp = None
  371.             try:
  372.                 fp = open(readmefile)
  373.                 contents = fp.read()
  374.                 # wax the last page, it contains Emacs cruft
  375.                 i = string.rfind(contents, '\f')
  376.                 if i > 0:
  377.                     contents = string.rstrip(contents[:i])
  378.             finally:
  379.                 if fp:
  380.                     fp.close()
  381.         except IOError:
  382.             sys.stderr.write("Couldn't open audiopy's README, "
  383.                              'using docstring instead.\n')
  384.             contents = __doc__ % globals()
  385.  
  386.         self.__text = text = Text(root, relief=SUNKEN,
  387.                                   width=80, height=24)
  388.         text.insert(0.0, contents)
  389.         scrollbar = Scrollbar(root)
  390.         scrollbar.pack(fill=Y, side=RIGHT)
  391.         text.pack(fill=BOTH, expand=YES)
  392.         text.configure(yscrollcommand=(scrollbar, 'set'))
  393.         scrollbar.configure(command=(text, 'yview'))
  394.  
  395.     def __withdraw(self, event=None):
  396.         self.__root.withdraw()
  397.  
  398.     def deiconify(self):
  399.         self.__root.deiconify()
  400.  
  401.  
  402.  
  403.  
  404. def usage(code, msg=''):
  405.     print __doc__ % globals()
  406.     if msg:
  407.         print msg
  408.     sys.exit(code)
  409.  
  410.  
  411. def main():
  412.     #
  413.     # Open up the audio control device and query for the current output
  414.     # device
  415.     device = sunaudiodev.open('control')
  416.  
  417.     if len(sys.argv) == 1:
  418.         # GUI
  419.         w = MainWindow(device)
  420.         try:
  421.             w.start()
  422.         except KeyboardInterrupt:
  423.             pass
  424.         return
  425.  
  426.     # spec:    LONG OPT, SHORT OPT, 0=input,1=output, MASK
  427.     options = [('--microphone', '-m', 0, MICROPHONE),
  428.                ('--linein',     '-i', 0, LINE_IN),
  429.                ('--headphones', '-p', 1, HEADPHONE),
  430.                ('--speaker',    '-s', 1, SPEAKER),
  431.                ('--lineout',    '-o', 1, LINE_OUT),
  432.                ]
  433.     # See the comment above about `CD'
  434.     try:
  435.         options.append(('--cd',         '-c', 0, CD))
  436.     except NameError:
  437.         pass
  438.  
  439.     info = device.getinfo()
  440.     # first get the existing values
  441.     i = 0
  442.     while i < len(sys.argv)-1:
  443.         i = i + 1
  444.         arg = sys.argv[i]
  445.         if arg in ('-h', '--help'):
  446.             usage(0)
  447.             # does not return
  448.         elif arg in ('-g', '--gain'):
  449.             gainspec = '<missing>'
  450.             try:
  451.                 gainspec = sys.argv[i+1]
  452.                 gain = int(gainspec)
  453.             except (ValueError, IndexError):
  454.                 usage(1, 'Bad gain specification: ' + gainspec)
  455.             info.o_gain = gain
  456.             i = i + 1
  457.             continue
  458.         elif arg in ('-v', '--version'):
  459.             print '''\
  460. audiopy -- a program to control the Solaris audio device.
  461. Contact: Barry Warsaw
  462. Email:   bwarsaw@python.org
  463. Version: %s''' % __version__            
  464.             sys.exit(0)
  465.         for long, short, io, mask in options:
  466.             if arg in (long, short):
  467.                 # toggle the option
  468.                 if io == 0: 
  469.                     info.i_port = info.i_port ^ mask
  470.                 else:
  471.                     info.o_port = info.o_port ^ mask
  472.                 break
  473.             val = None
  474.             try:
  475.                 if arg[:len(long)+1] == long+'=':
  476.                     val = int(arg[len(long)+1:])
  477.                 elif arg[:len(short)+1] == short+'=':
  478.                     val = int(arg[len(short)+1:])
  479.             except ValueError:
  480.                 usage(1, msg='Invalid option: ' + arg)
  481.                 # does not return
  482.             if val == 0:
  483.                 if io == 0:
  484.                     info.i_port = info.i_port & ~mask
  485.                 else:
  486.                     info.o_port = info.o_port & ~mask
  487.                 break
  488.             elif val == 1:
  489.                 if io == 0:
  490.                     info.i_port = info.i_port | mask
  491.                 else:
  492.                     info.o_port = info.o_port | mask
  493.                 break
  494.             # else keep trying next option
  495.         else:
  496.             usage(1, msg='Invalid option: ' + arg)
  497.     # now set the values
  498.     try:
  499.         device.setinfo(info)
  500.     except sunaudiodev.error, (code, msg):
  501.         if code <> errno.EINVAL:
  502.             raise
  503.     device.close()
  504.             
  505.  
  506.  
  507. if __name__ == '__main__':
  508.     main()
  509.